本身蠻喜歡地圖的,也想過要仔細研究GIS。
所以在認識到Mapbox和Leaflet時,就像挖到了寶藏。
其中前者是基於OSM的大型地圖服務;而後者正如其名,是個如葉子般輕量級的函式庫。像目前中央氣象局網站上的地圖就是以Leaflet所打造。
Mapbox本身有使用到Leaflet,後者的創辦人Volodymyr Agafonkin自己也在13年加入Mapbox,算是點「本是同根生」的小八卦。
網路上有不少根據Leaflet寫口罩地圖的教學資源,但卻沒看過很多從React Leaflet著手的。兩者的一大差異便是,名字有React的可以爽用宣告式語句。
心一橫,想說既然都要投入精神,就來闖比較少人走過的路吧。
這次打算來寫個關於下雨的地圖。
第一步先安裝react-leaflet。
然後拿官網提供的set up demo試試。
記得要import { MapContainer, Marker, Popup, TileLayer} from "react-leaflet";
誰曾想,最基礎的畫面卻render成了六親不認的樣子。
查找後發現,除了react-leaflet,leaflet本身仍要載呢。
還必須在css裡調整leaflet-container的高度。
感謝網友barbalex在issue#1052的回答。
看似一切順利。但等等,地標的icon怎麼不見了……
這算是React Leaflet的坑,沒有把Leaflet預設之圖例移植過來。
我先是在stackoverflow上找到看起來很合理的解法,但依舊無效。
發現邏輯上是更改圖片網址的前綴後,徑直把寫法簡化成了L.Icon.Default.imagePath = "https://unpkg.com/leaflet/dist/images/";
總算是有個樣子了。
這裡會需要import L from 'leaflet';
而這個L我們後續還會再碰見。
import "./styles.css";
import "leaflet/dist/leaflet.css";
import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
import L from "leaflet";
L.Icon.Default.imagePath = "https://unpkg.com/leaflet/dist/images/";
export default function App() {
return (
<MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[51.505, -0.09]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
);
}
接著把經緯度改成臺灣的位置;旋即更改布林值,解封滾輪限制。
漸入佳境。再來用好入門的useEffect抓天氣api。
const [data, setData] = useState();
useEffect(() => {
fetch(
"https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0002-001?Authorization=rdec-key-123-45678-011121314"
)
.then((res) => res.json())
.then((resJson) => setData(resJson))
.catch((err) => console.log(err));
}, []);
此時我希望能夠抓到「嘉義」測站即時的雨量資訊,但只要一重新整理就會出事。隨後就想到,對耶,filter也要寫在useEffect這邊才對。
import "./styles.css";
import "leaflet/dist/leaflet.css";
import { MapContainer, Marker, Popup, TileLayer } from "react-leaflet";
import { useState, useEffect } from "react";
import L from "leaflet";
L.Icon.Default.imagePath = "https://unpkg.com/leaflet/dist/images/";
export default function App() {
const [data, setData] = useState();
useEffect(() => {
fetch(
"https://opendata.cwa.gov.tw/api/v1/rest/datastore/O-A0002-001?Authorization=rdec-key-123-45678-011121314"
)
.then((res) => res.json())
.then(
(resJson) =>
resJson.records.Station.filter((s) => s.StationName === "嘉義")[0]
.RainfallElement.Now.Precipitation
)
.then((resJson) => setData(resJson))
.catch((err) => console.log(err));
}, []);
return (
<MapContainer center={[23.48, 120.45]} zoom={13} scrollWheelZoom={true}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker position={[23.48, 120.45]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
{JSON.stringify(data)}
</Popup>
</Marker>
</MapContainer>
);
}
中途我也有試過,如果想要嘉義市所有的測站資訊,該怎麼篩選。
.then((resJson) =>
resJson.records.Station.filter((s) =>
s.GeoInfo.CountyName.match("嘉義市")
)
)
重頭戲來了:我們要分開取得測站名、雨量和經緯度資訊。
跳出警告Invalid LatLng object: (undefined, 120.45),其實是useState裡忘記加上初始值0的基本錯誤。記住一個useEffect做一件事的原則,就不會有問題了。
結果程式碼寫著寫著,我發現icon又不見了?!
一查,原來這次是API的網址連不上。
是不是呼叫上限?或其他問題?
這時還沒確定,便決定先睡一覺再說吧。